package com.opencee.cloud.base.service.impl;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.opencee.boot.db.mybatis.service.impl.SupperServiceImpl;
import com.opencee.cloud.autoconfigure.utils.RestTemplateUtil;
import com.opencee.cloud.base.constants.BaseApiType;
import com.opencee.cloud.base.constants.BaseConstants;
import com.opencee.cloud.base.constants.BaseResourceType;
import com.opencee.cloud.base.entity.BaseApiEntity;
import com.opencee.cloud.base.event.ApiOfflineEvent;
import com.opencee.cloud.base.event.ApiPublishEvent;
import com.opencee.cloud.base.mapper.BaseApiMapper;
import com.opencee.cloud.base.service.IBaseApiService;
import com.opencee.cloud.base.service.IBasePrivilegesService;
import com.opencee.cloud.base.vo.BaseApiMetaVO;
import com.opencee.cloud.base.vo.BaseApiVO;
import com.opencee.common.exception.BaseFailException;
import com.opencee.common.utils.RedisTemplateUtil;
import io.swagger.models.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * API接口 服务实现类
 * </p>
 *
 * @author liuyadu
 * @since 2021-04-16
 */
@Service
public class BaseApiServiceImpl extends SupperServiceImpl<BaseApiMapper, BaseApiEntity> implements IBaseApiService {

    @Autowired
    private IBasePrivilegesService privilegesService;

    @Autowired
    private RestTemplateUtil restTemplateUtil;
    @Autowired
    private RedisTemplateUtil redisTemplateUtil;
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private BusProperties busProperties;

    /**
     * 检测是否存在
     *
     * @param code
     * @param type
     * @return
     */
    @Override
    public boolean exists(String code, String type) {
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getCode, code)
                .eq(BaseApiEntity::getType, type);
        return super.count(wrapper) > 0;
    }

    /**
     * 获取
     *
     * @param code
     * @param type
     * @return
     */
    @Override
    public BaseApiEntity getBy(String code, String type) {
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getCode, code)
                .eq(BaseApiEntity::getType, type);
        return super.getOne(wrapper);
    }

    /**
     * 添加
     *
     * @param api
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(BaseApiVO api) {
        Assert.hasText(api.getName(), "名称不能为空");
        Assert.hasText(api.getCode(), "编码不能为空");
        Assert.hasText(api.getType(), "类型不能为空");
        Assert.hasText(api.getSid(), "系统ID不能为空");
        Assert.notNull(BaseApiType.getByValue(api.getType()), "类型不支持:" + api.getType());
        Assert.hasText(api.getAuthority(), "权限名称不能为空");
        String prefix = api.getType() + BaseConstants.SEPARATOR;
        Assert.isTrue(api.getAuthority().startsWith(prefix), "权限名称必须以[" + prefix + "]开头");
        if (exists(api.getCode(), api.getType())) {
            throw new BaseFailException(String.format("编码(%s)已存在!", api.getCode()));
        }
        if (api.getSort() == null) {
            api.setSort(0);
        }
        if (api.getStatus() == null) {
            api.setStatus(1);
        }
        BaseApiEntity entity = new BaseApiEntity();
        entity.setName(api.getName());
        entity.setCode(api.getCode());
        entity.setType(api.getType());
        entity.setSid(api.getSid());
        entity.setPath(api.getPath());
        entity.setParentId(api.getParentId());
        entity.setMetaInfo(api.getMeta().toJSONString());
        entity.setAuthority(api.getAuthority());
        entity.setRemark(api.getRemark());
        entity.setSort(api.getSort());
        entity.setStatus(api.getStatus());
        boolean flag = super.save(entity);
        api.setId(entity.getId());
        // 更新缓存
        putCache(entity);
        return flag;
    }

    /**
     * 更新
     *
     * @param api
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean update(BaseApiVO api) {
        Assert.hasText(api.getName(), "名称不能为空");
        Assert.hasText(api.getCode(), "编码不能为空");
        Assert.hasText(api.getType(), "类型不能为空");
        Assert.hasText(api.getSid(), "系统ID不能为空");
        Assert.notNull(BaseApiType.getByValue(api.getType()), "类型不支持:" + api.getType());
        Assert.hasText(api.getAuthority(), "权限名称不能为空");
        String prefix = api.getType() + BaseConstants.SEPARATOR;
        Assert.isTrue(api.getAuthority().startsWith(prefix), "权限名称必须以[" + prefix + "]开头");
        if (api.getSort() == null) {
            api.setSort(0);
        }
        if (api.getStatus() == null) {
            api.setStatus(1);
        }
        BaseApiEntity saved = getById(api.getId());
        if (saved == null) {
            if (!saved.getCode().equals(api.getCode()) || !saved.getType().equals(api.getType())) {
                // 和原来不一致重新检查唯一性
                if (exists(api.getCode(), api.getType())) {
                    throw new BaseFailException(String.format("编码(%s)已存在!", api.getCode()));
                }
            }
        }
        saved.setName(api.getName());
        saved.setCode(api.getCode());
        saved.setType(api.getType());
        saved.setPath(api.getPath());
        saved.setParentId(api.getParentId());
        saved.setSid(api.getSid());
        saved.setMetaInfo(api.getMeta().toJSONString());
        saved.setAuthority(api.getAuthority());
        saved.setRemark(api.getRemark());
        saved.setSort(api.getSort());
        saved.setStatus(api.getStatus());
        saved.setUpdateTime(new Date());
        boolean flag = updateById(saved);
        // 级联更新状态
        updateChildrenStatus(saved.getId(), saved.getStatus());
        api.setId(saved.getId());
        // 更新缓存
        putCache(saved);
        return flag;
    }

    private void putCache(BaseApiEntity entity) {
        if (entity != null && StringUtils.isNotBlank(entity.getPath())) {
            String key = entity.getPath() + BaseConstants.SEPARATOR + entity.getSid();
            redisTemplateUtil.hset(BaseConstants.CACHE_API_MAP_KEY, key, entity);
        }
    }

    private void removeCache(BaseApiEntity entity) {
        if (entity != null && StringUtils.isNotBlank(entity.getPath())) {
            String key = entity.getPath() + BaseConstants.SEPARATOR + entity.getSid();
            redisTemplateUtil.hdel(BaseConstants.CACHE_API_MAP_KEY, key);
        }
    }

    /**
     * 更新子级状态
     *
     * @param parentId
     * @param status
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateChildrenStatus(Long parentId, Integer status) {
        if (status != 0) {
            return false;
        }
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getParentId, parentId);
        BaseApiEntity entity = new BaseApiEntity();
        entity.setStatus(status);
        return update(entity, wrapper);
    }

    /**
     * 查询列表
     *
     * @param sid
     * @param status
     * @return
     */
    @Override
    public List<BaseApiVO> list(String sid, Integer status, BaseApiType type) {
        Map<String, Object> map = new HashMap<>(8);
        map.put("sid", sid);
        map.put("status", status);
        if (type != null) {
            map.put("type", type.getValue());
        }
        return baseMapper.selectApiVo(map);
    }

    /**
     * 批量id查询转map
     *
     * @param ids
     * @return
     */
    @Override
    public Map<Long, BaseApiEntity> mapByIds(Set<Long> ids) {
        Map<Long, BaseApiEntity> map = new HashMap<>(8);
        if (ids.isEmpty()) {
            return map;
        }
        List<BaseApiEntity> list = listByIds(ids);
        list.forEach(t -> {
            map.put(t.getId(), t);
        });
        return map;
    }


    private boolean isHttpUrl(String url) {
        return url.startsWith("http://") || url.startsWith("https://");
    }

    /**
     * 同步swagger接口
     *
     * @param serviceId 服务标识
     * @param path      swagger 文档地址 /v2/api-docs 或 http://localhost:8080/v2/api-docs
     * @param basePath
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Integer importSwagger(String serviceId, String path, String basePath) {
        // 请求swagger接口文档
        String docJson = "";
        if (isHttpUrl(path)) {
            docJson = restTemplateUtil.get(path, null, null, String.class);
        } else {
            docJson = restTemplateUtil.getService(serviceId, path, null, null, String.class);
        }
        if (StringUtils.isBlank(docJson)) {
            throw new BaseFailException("接口文档不存在");
        }
        JSONObject jsonObject = JSON.parseObject(docJson, Feature.DisableCircularReferenceDetect);
        JSONObject info = jsonObject.getJSONObject("info");
        Swagger swagger = jsonObject.toJavaObject(Swagger.class);
        // 接口分组
        Map<String, BaseApiEntity> apiGroupMap = new HashMap<>(8);
        // 根据描述分组description,防止同一个类,设置了多个标签
        Map<String, List<Tag>> groupMap = swagger.getTags().stream().collect(Collectors.groupingBy(tag -> tag.getDescription()));

        groupMap.values().stream().forEach(tags -> {
            tags.stream().forEach(tag -> {
                String description = tag.getDescription();
                BaseApiEntity entity = apiGroupMap.get(description);
                String code = SecureUtil.md5(serviceId + tag.getDescription());
                entity = this.getBy(code, BaseApiType.GROUP.getValue());
                if (entity == null) {
                    entity = new BaseApiEntity();
                    entity.setSid(serviceId);
                    entity.setType(BaseApiType.GROUP.getValue());
                    entity.setParentId(0L);
                    entity.setSort(0);
                    entity.setStatus(0);
                    entity.setName(tag.getName());
                    entity.setCode(code);
                    entity.setAuthority(BaseApiType.GROUP.getValue() + BaseConstants.SEPARATOR + entity.getCode());
                    entity.setRemark(tag.getDescription());
                    entity.setCreateTime(new Date());
                    entity.setUpdateTime(entity.getCreateTime());
                    // 提前保存,获取id
                    save(entity);
                } else {
                    entity.setName(tag.getName());
                    entity.setRemark(tag.getDescription());
                    entity.setUpdateTime(new Date());
                    updateById(entity);
                }
                if (!apiGroupMap.containsKey(tag.getName())) {
                    // 放入分组
                    apiGroupMap.put(tag.getName(), entity);
                }
            });
        });

        Map<String, Path> map = swagger.getPaths();
        TreeMap<String, Path> treeMap = new TreeMap<>(map);
        Iterator<Map.Entry<String, Path>> iterator = treeMap.entrySet().iterator();
        List<BaseApiEntity> list = new ArrayList<>();
        while (iterator.hasNext()) {
            BaseApiEntity parent = new BaseApiEntity();
            Map.Entry<String, Path> entry = iterator.next();
            String name = "";
            String remark = "";
            String namePath = "";
            String serverPath = entry.getKey();


            Path value = entry.getValue();
            Map<HttpMethod, Operation> operationMap = value.getOperationMap();
            Iterator<Map.Entry<HttpMethod, Operation>> operationIterator = operationMap.entrySet().iterator();
            Set<String> methods = new HashSet<>();
            while (operationIterator.hasNext()) {
                Map.Entry<HttpMethod, Operation> operationEntry = operationIterator.next();
                HttpMethod method = operationEntry.getKey();
                methods.add(method.name());
                Operation operation = operationEntry.getValue();
                if (StringUtils.isBlank(name) && StringUtils.isNotBlank(operation.getSummary())) {
                    name = operation.getSummary();
                }
                if (StringUtils.isBlank(remark) && StringUtils.isNotBlank(operation.getDescription())) {
                    remark = operation.getDescription();
                }
                List<String> tags = operation.getTags();
                if (!CollectionUtils.isEmpty(tags) && StringUtils.isBlank(namePath)) {
                    namePath = String.format("%s / %s", tags.get(0), name);
                    parent = apiGroupMap.get(tags.get(0));
                }
            }
            String requestPath = (basePath + serverPath).replaceAll("//", "/");
            String code = SecureUtil.md5(serviceId + requestPath);
            String authority = BaseApiType.API.getValue() + BaseConstants.SEPARATOR + code;
            BaseApiEntity entity = getBy(code, BaseApiType.API.getValue());
            BaseApiMetaVO meta = new BaseApiMetaVO();
            meta.setScheme("http://");
            meta.setMethod(methods);
            meta.setBasePath(basePath);
            meta.setAuth(1);
            // 后端服务类型:1-服务负载, 2-网络地址
            meta.setServerType("1");
            // 后端服务名
            meta.setServerName(serviceId);
            // 后端请求Path
            meta.setServerPath(serverPath);


            if (entity == null) {
                entity = new BaseApiEntity();
                entity.setStatus(0);
                entity.setSort(0);
                if (parent != null) {
                    entity.setParentId(parent.getId());
                } else {
                    entity.setParentId(0L);
                }
                entity.setCreateTime(new Date());
                entity.setUpdateTime(entity.getCreateTime());
                entity.setMetaInfo(JSONObject.toJSONString(meta));
            } else {
                entity.setUpdateTime(new Date());
                BaseApiMetaVO metaObj = JSONObject.parseObject(entity.getMetaInfo(), BaseApiMetaVO.class);
                BeanUtils.copyProperties(meta, metaObj);
                entity.setMetaInfo(JSONObject.toJSONString(metaObj));
            }
            entity.setCode(code);
            entity.setSid(serviceId);
            entity.setPath(requestPath);
            entity.setType(BaseApiType.API.getValue());
            entity.setAuthority(authority);
            entity.setName(name);
            entity.setRemark(remark);
            list.add(entity);
        }

        if (!CollectionUtils.isEmpty(list)) {
            saveOrUpdateBatch(list);
        }
        return list.size();
    }

    /**
     * 批量删除
     *
     * @param sid
     * @param types
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean removeBy(String sid, Set<String> types) {
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getSid, sid)
                .in(BaseApiEntity::getType, types);
        Set<Long> ids = list(wrapper).stream().map(t -> t.getId()).collect(Collectors.toSet());
        boolean flag = remove(wrapper);
        if (flag) {
            // 级联删除授权
            privilegesService.removeByGrantIds(BaseResourceType.API, ids);
            this.offline(ids);
        }
        return flag;
    }

    /**
     * 根据父节点删除
     *
     * @param parentId
     * @return
     */
    @Override
    public Boolean removeByParentId(Long parentId) {
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getParentId, parentId);
        boolean flag = remove(wrapper);
        return flag;
    }


    @Override
    public boolean removeById(Serializable id) {
        BaseApiEntity entity = getById(id);
        // 移除缓存
        removeCache(entity);
        boolean flag = super.removeById(id);
        if (flag) {
            // 级联删除
            this.removeByParentId((Long) id);
            privilegesService.removeByGrantIds(BaseResourceType.API, new HashSet(Arrays.asList(id)));
            this.offline(new HashSet(Arrays.asList(id)));
        }
        return flag;
    }

    @Override
    public boolean removeByIds(Collection<?> idList) {
        if (CollectionUtils.isEmpty(idList)) {
            return false;
        }
        idList.forEach(t -> {
            BaseApiEntity entity = getById((Serializable) t);
            // 移除缓存
            removeCache(entity);
        });
        boolean flag = super.removeByIds(idList);
        if (flag) {
            // 级联删除
            QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
            wrapper.lambda().in(BaseApiEntity::getParentId, idList);
            privilegesService.removeByGrantIds(BaseResourceType.API, new HashSet(idList));
            this.offline(new HashSet(idList));
        }
        return flag;
    }


    @Override
    public BaseApiEntity getByPath(String path, String sid) {
        String key = path + BaseConstants.SEPARATOR + sid;
        BaseApiEntity entity = (BaseApiEntity) redisTemplateUtil.hget(BaseConstants.CACHE_API_MAP_KEY, key);
        if (entity != null) {
            return entity;
        }
        QueryWrapper<BaseApiEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(BaseApiEntity::getSid, sid)
                .eq(BaseApiEntity::getPath, path);
        entity = getOne(wrapper);
        if (entity != null) {
            redisTemplateUtil.hset(BaseConstants.CACHE_API_MAP_KEY, key, entity);
        }
        return entity;
    }

    /**
     * 发布
     *
     * @param apiIds
     * @return
     */
    @Override
    public void publish(Set<Long> apiIds) {
        if (CollectionUtils.isEmpty(apiIds)) {
            return;
        }
        List<BaseApiEntity> entityList = listByIds(apiIds);
        List<BaseApiVO> list = entityList.stream().map(t -> {
            BaseApiVO vo = new BaseApiVO();
            BeanUtils.copyProperties(t, vo);
            return vo;
        }).collect(Collectors.toList());

        eventPublisher.publishEvent(new ApiPublishEvent(this, busProperties.getId(), list));
    }

    /**
     * 下线
     *
     * @param apiIds
     */
    @Override
    public void offline(Set<Long> apiIds) {
        if (CollectionUtils.isEmpty(apiIds)) {
            return;
        }
        eventPublisher.publishEvent(new ApiOfflineEvent(this, busProperties.getId(), apiIds));
    }

    @Override
    public boolean saveOrUpdateBatch(Collection<BaseApiEntity> entityList) {
        boolean flag = super.saveOrUpdateBatch(entityList);
        entityList.forEach(t -> {
            putCache(t);
        });
        return flag;
    }
}
